/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.worldmodel;

import static java.lang.Math.asin;
import static java.lang.Math.sqrt;

import java.util.LinkedList;

import robocup.component.WorldModel;
import robocup.component.geometry.Vektor;
import robocup.component.infotypes.CoachSayInfo;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.RefereeInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.speechacts.coachlanguage.ActivationElement;
import robocup.component.speechacts.coachlanguage.CPoint;
import robocup.component.speechacts.coachlanguage.Condition;
import robocup.component.speechacts.coachlanguage.DefineDirective;
import robocup.component.speechacts.coachlanguage.DefineMessage;
import robocup.component.speechacts.coachlanguage.DefineRegion;
import robocup.component.speechacts.coachlanguage.DefineRule;
import robocup.component.speechacts.coachlanguage.DeleteMessage;
import robocup.component.speechacts.coachlanguage.Directive;
import robocup.component.speechacts.coachlanguage.Region;
import robocup.component.speechacts.coachlanguage.RuleMessage;
import robocup.component.speechacts.coachlanguage.TypeConstants;
import robocup.component.speechacts.coachlanguage.UnumSet;

/**
 * The CLangModel contains all information received from the coach via
 * CLang (except freeform messages). The model also contains the logic to 
 * evaluate rules, which were defined by the coach.
 */
public class CLangModel implements IModel {

	/**
	 * The agents current world-model
	 */
	private WorldModel world = null;

	/**
	 * The list of received Clang-Messages
	 */
	private LinkedList<CoachSayInfo> clangList = new LinkedList<CoachSayInfo>();

	/**
	 * The list of activated rules.
	 */
	private LinkedList<DefineRule> ruleListOn = new LinkedList<DefineRule>();

	/**
	 * The list of inactive rules.
	 */
	private LinkedList<DefineRule> ruleListOff = new LinkedList<DefineRule>();

	/**
	 * A model containing all CLANG-Information perceived from the own coach so
	 * far.
	 * 
	 * @param world -
	 *            the current world-model
	 */
	public CLangModel(WorldModel world) {
		this.world = world;
	}

	/**
	 * This method is invoked after a new coach say info arrival. Depending
	 * on the type of the message, different actions are executed.
	 * 
	 * @param info - the CoachSayInfo containing the clang-statement
	 */
	public void update(CoachSayInfo info) {
		TypeConstants.MESSAGETYPE type = info.getMessageType();

		switch (type) {

		case DELETE:
			this.deleteRule(info.getDeleteMessage());
			break;

		case RULE:
			this.ruleActivation(info.getRuleMessage());
			break;

		case DEFINE:

			for (DefineMessage dm : info.getDefineList()) {
				TypeConstants.DEFINETYPE define = dm.getDefineType();

				switch (define) {

				case DEFINED:
					this.evaluateDirectiveMessage(dm.getDefineDirective());
					break;

				case DEFINER:
					this.evaluateRegionMessage(dm.getDefineRegion());
					break;

				case DEFINERULE:
					this.ruleListOff.add(dm.getDefineRule());
					break;

				default:
					System.out.println(info.toString());
					break;
				}

			}

			break;

		default:
			System.out.println(info.toString());
			break;

		}

	}

	/**
	 * Handles the evaluation of a DefineRegion statement. Currently,
	 * only points are evaluated.
	 * 
	 * @param dr - the DefineReagon
	 */
	public void evaluateRegionMessage(DefineRegion dr) {
		this.evaluateRegion(dr.getRegion());
	}

	/**
	 * Handles the evaluation of a Region statement. Currently,
	 * only points are evaluated. 
	 * 
	 * @param reg - the Region to be evaluated.
	 */
	public void evaluateRegion(Region reg) {
		TypeConstants.REGIONTYPE type = reg.getRegionType();

		switch (type) {

		case POINT:
			this.evaluatePoint(reg.getPoint());

			break;

		}
	}

	/**
	 * Extracts point-data and returns the corresponding vektor.
	 * 
	 * @param pt - the Point
	 * @return the point as usual Vektor
	 */
	public Vektor evaluatePoint(CPoint pt) {

		TypeConstants.POINTTYPE type = pt.getPointType();

		Vektor v = new Vektor();

		switch (type) {

		case XY:
			v.setX(pt.getX());
			v.setY(pt.getY());
			break;
		}
		return v;

	}

	/**
	 * Evaluates directives. Currently, no directives are implemented.
	 * 
	 * @param dd - the defined directive
	 */
	public void evaluateDirectiveMessage(DefineDirective dd) {

	}

	/**
	 * Activates the given rule.
	 * 
	 * @param ruleMessage - the rule to activate
	 */
	public void ruleActivation(RuleMessage ruleMessage) {
		LinkedList<ActivationElement> activationList = ruleMessage
				.getActivationList();

		for (ActivationElement ae : activationList) {
			LinkedList<String> ruleList = ae.getRuleList().getIdList();
			if (ae.isRuleOn()) {
				if (ruleList.getFirst().matches("all"))
					for (DefineRule rule : this.ruleListOff) {
						this.ruleListOn.add(rule);
						this.ruleListOff.remove(rule);
					}
				else
					for (String name : ruleList) {
						for (DefineRule rule : this.ruleListOff) {
							if (name.matches(rule.getRuleName())) {
								this.ruleListOn.add(rule);
								this.ruleListOff.remove(rule);
							}
						}

					}

			} else {
				if (ruleList.getFirst().matches("all"))
					for (DefineRule rule : this.ruleListOn) {
						this.ruleListOff.add(rule);
						this.ruleListOn.remove(rule);
					}
				else
					for (String name : ruleList) {
						for (DefineRule rule : this.ruleListOn) {
							if (name.matches(rule.getRuleName())) {
								this.ruleListOff.add(rule);
								this.ruleListOn.remove(rule);
							}
						}
					}

			}
		}
	}

	/**
	 * Deletes the given rule.
	 * 
	 * @param deleteMessage - the rule to delete.
	 */
	public void deleteRule(DeleteMessage deleteMessage) {

		LinkedList<String> deleteList = deleteMessage.getDeleteList()
				.getIdList();

		for (String name : deleteList) {
			for (DefineRule rule : this.ruleListOff) {
				if (name.matches(rule.getRuleName()))
					this.ruleListOff.remove(rule);
			}
		}

	}

	/**
	 * Adds the given CoachSayInfo to history.
	 * 
	 * @param coachSayInfo - the new CoachSayInfo.
	 */
	public void addCoachSayInfo(CoachSayInfo coachSayInfo) {
		this.clangList.add(coachSayInfo);
	}

	/**
	 * This method evaluates the condition of a rule.
	 * 
	 * @return true, if a condition of any active rule is satisfied.
	 */
	public boolean evaluateRuleCondition() {

		boolean evaluation = false;

		for (DefineRule dr : this.ruleListOn) {
			if (this.evaluateCondition(dr.getRule().getCondition())) {
				evaluation = true;
				this.evaluateDirectiveList(dr.getRule().getDirectiveList());
			}
		}

		return evaluation;
	}

	/**
	 * This method evaluates all directives.
	 * 
	 * @param directiveList - the list of directives to evaluate.
	 */
	public void evaluateDirectiveList(LinkedList<Directive> directiveList) {

	}

	/**
	 * Checks, if a player belongs to a UnumSet
	 * 
	 * @param player - the player
	 * @param unumSet - the UnumSet
	 * @return true, if the player belongs to the specified set.
	 */
	public boolean isInUnumSet(int player, UnumSet unumSet) {

		for (int i : unumSet.getUnumIntList()) {
			if (player == i)
				return true;
		}
		return false;
	}

	/**
	 * Evaluates a single directive, currently unused.
	 * 
	 * @param directive - the directive to evaluate.
	 */
	public void evaluateDirective(Directive directive) {

		if (isInUnumSet(world.getSelfRef().getNumber(), directive.getUnumSet())) {
			// do anything
		}
	}

	/**
	 * Checks, if a given point is inside a triangle.
	 * 
	 * @param tri1 - first point of the triangle
	 * @param tri2 - second point of the triangle
	 * @param tri3 - third point of the triangle
	 * @param x - the x coordinate of the corresponding point
	 * @param y - the y coordinate of the corresponding point
	 * @return true, if <x,y> is inside triangle(tri1,tri2,tri3)
	 */
	public boolean isPosinTri(CPoint tri1, CPoint tri2, CPoint tri3, double x,
			double y) {
		double sign1 = (tri2.getY() - tri1.getY()) * (x - tri1.getX())
				- (tri2.getX() - tri1.getX()) * (tri1.getY() - y);
		double sign2 = (tri3.getY() - tri2.getY()) * (x - tri2.getX())
				- (tri3.getX() - tri2.getX()) * (tri2.getY() - y);
		double sign3 = (tri1.getY() - tri3.getY()) * (x - tri3.getX())
				- (tri1.getX() - tri3.getX()) * (tri3.getY() - y);
		if ((sign1 >= 0 && sign2 >= 0 && sign3 >= 0)
				|| (sign1 <= 0 && sign2 <= 0 && sign3 <= 0))
			return true;
		else
			return false;
	}

	/**
	 * Checks, if a given position (Vektor) is inside a given region
	 * 
	 * @param vector - the vektor, which is checked to be somewhere inside
	 * @param region - the region
	 * @return true, if <code>vector</code> is inside <code>region</code>, 
	 * depending on the region type.
	 */
	public boolean isPosinRegion(Vektor vector, Region region) {
		double x = vector.getX();
		double y = vector.getY();
		switch (region.getRegionType()) {
		case INIT:
			return false;
		// case NAME:
		case NULL:
			return false;
		case POINT:
			if (x == region.getPoint().getX() && y == region.getPoint().getY())
				return true;
			else
				return false;
		case ARC:
			double d = sqrt((x - region.getArcPoint().getX())
					* (x - region.getArcPoint().getX())
					+ (y - region.getArcPoint().getY())
					* (y - region.getArcPoint().getY()));
			if (d <= region.getArcReal2() && d >= region.getArcReal1()) {
				if (d != 0) {
					double angle = 360 * asin(y - region.getArcPoint().getY())
							/ d;
					if (angle <= region.getArcReal4()
							&& angle >= region.getArcReal3())
						return true;
					else
						return false;
				} else
					return true;
			} else
				return false;
		case REC:
			double xMax,
			xMin,
			yMax,
			yMin;
			xMax = (region.getRecPoint2().getX() >= region.getRecPoint1()
					.getX()) ? region.getRecPoint2().getX() : region
					.getRecPoint1().getX();
			xMin = (region.getRecPoint2().getX() <= region.getRecPoint1()
					.getX()) ? region.getRecPoint2().getX() : region
					.getRecPoint1().getX();
			yMax = (region.getRecPoint2().getY() >= region.getRecPoint1()
					.getY()) ? region.getRecPoint2().getY() : region
					.getRecPoint1().getY();
			yMin = (region.getRecPoint2().getY() <= region.getRecPoint1()
					.getY()) ? region.getRecPoint2().getY() : region
					.getRecPoint1().getY();
			if (x <= xMax && x >= xMin && y <= yMax && y >= yMin)
				return true;
			else
				return false;
		case TRI:
			CPoint tri1 = region.getTriPoint1();
			CPoint tri2 = region.getTriPoint2();
			CPoint tri3 = region.getTriPoint3();
			return this.isPosinTri(tri1, tri2, tri3, x, y);
		case QUAD:
			CPoint quad1 = region.getQuadPoint1();
			CPoint quad2 = region.getQuadPoint2();
			CPoint quad3 = region.getQuadPoint3();
			CPoint quad4 = region.getQuadPoint4();
			return this.isPosinTri(quad1, quad2, quad3, x, y)
					&& this.isPosinTri(quad3, quad4, quad1, x, y);
		case REG:
			for (Region reg : region.getRegionList())
				if (this.isPosinRegion(vector, reg))
					return true;
			return false;
		default:
			return false;

		}
	}

	/**
	 * This method evaluates comparative statements such as &gt;, &lt;,
	 * ==, !=, etc.
	 * @param A - the first number
	 * @param B - the second number
	 * @param com - the comparator function (e.g. ==) as string
	 * @return
	 */
	public boolean compare(int A, int B, String com) {
		if (com == "<")
			return A < B ? true : false;
		else if (com == ">")
			return A > B ? true : false;
		else if (com == ">=")
			return A >= B ? true : false;
		else if (com == "<=")
			return A <= B ? true : false;
		else if (com == "==")
			return A == B ? true : false;
		else if (com == "!=")
			return A != B ? true : false;
		else
			return false;

	}

	/**
	 * This method evaluates a comparation statement.
	 * Supported are Time, Goals and Goal-Diff statements.
	 * 
	 * @param com - the compare-statement
	 * @return true, if condition is satisfied
	 */
	public boolean evaluateCompare(Condition com) {
		switch (com.getCondCompType()) {
		case TIME:
			return compare(this.world.getCycle(), com.getCondCompInt(), com
					.getCondComp());
		case OPP_GOALS:
			return compare(this.world.getGoalsOther(), com.getCondCompInt(),
					com.getCondComp());
		case OUR_GOALS:
			return compare(this.world.getGoalsTeam(), com.getCondCompInt(), com
					.getCondComp());
		case GOAL_DIFF:
			return compare(this.world.getGoalDiff(), com.getCondCompInt(), com
					.getCondComp());
		default:
			return false;
		}
	}

	/**
	 * This method evaluates any condition, depending on the type.
	 * 
	 * @param condition - the given condition.
	 * @return true, if satisfied
	 */
	public boolean evaluateCondition(Condition condition) {
		TypeConstants.CONDITIONTYPE type = condition.getConditionType();

		switch (type) {

		case TRUE:
			return true;

		case FALSE:
			return false;

		case PLAYM:
			return (this.world.getPlayMode() == condition.getPlayMode());

		case NOT:
			return (!this.evaluateCondition(condition.getNot()));

		case AND:
			boolean and = true;
			for (Condition cond : condition.getAnd()) {
				and = this.evaluateCondition(cond);
				if (!and)
					break;
			}
			return and;

		case OR:
			boolean or = false;
			for (Condition cond : condition.getAnd()) {
				or = this.evaluateCondition(cond);
				if (or)
					break;
			}
			return or;

		case BPOS:
			Region bposRegion = condition.getRegion();
			Vektor bpos = this.world.getBallRef().getPosition();
			return this.isPosinRegion(bpos, bposRegion);
		// case PPOS:
		// Region pposRegion = condition.getRegion();
		case CONDCOMP:
			return this.evaluateCompare(condition);
		// case UNUM:
		// buf.append("(unum " + this.unumString + this.unumSet.toString() +
		// ")");
		// break;
		case BOWNER:
			if (condition.isTeamOur()) {
				// UnumSet bowner = condition.getUnumSet();
				// for (int intElement : bowner.getUnumIntList()){
				//					
				// }
				// if( intElement == this.world.getBallRef().
			} else {

			}
		}

		return false;
	}


	/**
	 * Update model after BS arrival. Not used here.
	 * 
	 * @param info - a sensebody info
	 */
	public void update(SenseBodyInfo info) {
	}

	/**
	 * Update model after VI arrival. Not used here
	 * 
	 * @param info - a visual info
	 */
	public void update(VisualInfo info) {
	}

	/**
	 * Update model after referee info arrival. Not used here.
	 * 
	 * @param info - a referee info
	 */
	public void update(RefereeInfo info) {
	}

	/**
	 * Update model after play mode info arrival.
	 * Not used here.
	 * 
	 * @param info - a playmode info
	 */
	public void update(PlayModeInfo info) {
	}

	/**
	 * Update model after player say info arrival.
	 * Not used here.
	 * 
	 * @param info - a playersay-info.
	 */
	public void update(PlayerSayInfo info) {
	}


}
